﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Xml;
using Slb.Ocean.Core;
using System.Reflection;
using System.Runtime.Serialization;

namespace Public.Ocean.Petrel.Extensions.Internal
{
    public class StringValueConverter
    {
        Dictionary<Type, Func<string, object>> valueGetters;
        Dictionary<Type, Func<object, string>> valueSetters;

        List<KeyValuePair<Type, Func<string, Type, object>>> referenceGetters;
        List<KeyValuePair<Type, Func<object, string>>> referenceSetters;

        public StringValueConverter()
        {
            valueGetters = new Dictionary<Type, Func<string, object>>();
            valueSetters = new Dictionary<Type, Func<object, string>>();
            referenceGetters = new List<KeyValuePair<Type, Func<string, Type, object>>>();
            referenceSetters = new List<KeyValuePair<Type, Func<object, string>>>();

            InstallValue<string>(v => v, v => v);
            InstallValue(v => v.ToLower() == true.ToString(CultureInfo.InvariantCulture).ToLower(), v => v.ToString(CultureInfo.InvariantCulture));
            InstallValue(v => byte.Parse(v, CultureInfo.InvariantCulture), v => v.ToString(CultureInfo.InvariantCulture));
            InstallValue(v => int.Parse(v, CultureInfo.InvariantCulture), v => v.ToString(CultureInfo.InvariantCulture));
            InstallValue(v => long.Parse(v, CultureInfo.InvariantCulture), v => v.ToString(CultureInfo.InvariantCulture));
            InstallValue(v => float.Parse(v, CultureInfo.InvariantCulture), v => v.ToString(CultureInfo.InvariantCulture));
            InstallValue(v => double.Parse(v, CultureInfo.InvariantCulture), v => v.ToString(CultureInfo.InvariantCulture));
            InstallValue(v => Decimal.Parse(v, CultureInfo.InvariantCulture), v => v.ToString(CultureInfo.InvariantCulture));
            InstallValue(v => XmlConvert.ToDateTime(v, XmlDateTimeSerializationMode.RoundtripKind), v => XmlConvert.ToString(v, XmlDateTimeSerializationMode.RoundtripKind));
            InstallValue(v => new Droid(v), v => v.ToString());

            InstallReference<Enum>((v, t) => Enum.Parse(t, v) as Enum, v => v.ToString());
            InstallReference<IIdentifiable>((v, t) => DataManager.Resolve(new Droid(v)) as IIdentifiable, v => v.Droid.ToString());
            InstallReference<Uri>((v, t) => new Uri(v), v => v.ToString());
            InstallReference<object>((v, t) => ObjFromString(v), v => ObjToString(v));
        }

        public string Pack<T>(T value)
        {
            if (value == null) return null;
            Type valueType = typeof(T);

            Func<object, string> valueSetter;
            if (valueSetters.TryGetValue(valueType, out valueSetter))
            {
                return valueSetter(value);
            }

            foreach (var referenceSetter in referenceSetters)
            {
                if (referenceSetter.Key.IsAssignableFrom(valueType))
                {
                    return referenceSetter.Value(value);
                }
            }

            return null;
        
        }

        public T Unpack<T>(string text)
        {
            return Unpack<T>(text, default(T));
        }

        public T Unpack<T>(string text, T valueIfMissing)
        {
            if (string.IsNullOrEmpty(text)) return valueIfMissing;
            Type valueType = typeof(T);

            Func<string, object> valueGetter;
            if (valueGetters.TryGetValue(valueType, out valueGetter))
            {
                return (T)valueGetter(text);
            }

            foreach (var referenceGetter in referenceGetters)
            {
                if (referenceGetter.Key.IsAssignableFrom(valueType))
                {
                    return (T)referenceGetter.Value(text, typeof(T));
                }
            }

            return valueIfMissing;
        }

        void InstallValue<T>(Func<string, T> getter, Func<T, string> setter)
        {
            valueGetters.Add(typeof(T), text => getter(text));
            valueSetters.Add(typeof(T), value => setter((T)value));

            valueGetters.Add(typeof(T[]), text => Split<T>(text, typeof(T), (v,t) => getter(v)));
            valueSetters.Add(typeof(T[]), value => Join<T>((T[])value, setter));
        }

        void InstallReference<T>(Func<string, Type, T> getter, Func<T, string> setter) where T : class
        {
            referenceGetters.Add(new KeyValuePair<Type, Func<string, Type, object>>(typeof(T), (text, type) => getter(text, type)));
            referenceSetters.Add(new KeyValuePair<Type, Func<object, string>>(typeof(T), value => setter(value as T)));

            referenceGetters.Add(new KeyValuePair<Type, Func<string, Type, object>>(typeof(T[]), (text, type) => Split<T>(text, type, getter)));
            referenceSetters.Add(new KeyValuePair<Type, Func<object, string>>(typeof(T[]), value => Join<T>(value as T[], setter)));
        }

        static string Join<T>(IEnumerable<T> values, Func<T, string> setter)
        {
            StringBuilder result = new StringBuilder();
            foreach (var value in values)
            {
                if (result.Length > 0) { result.Append(", "); }
                result.Append(setter(value));
            }
            return result.ToString();
        }

        static T[] Split<T>(string inputs, Type type, Func<string, Type, T> getter)
        {
            List<T> result = new List<T>();
            foreach (var input in inputs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                result.Add(getter(input.TrimStart(), type));
            }
            return result.ToArray();
        }

        string ObjToString(object value)
        {
            return string.Format("{0}:{1}", value.GetType().AssemblyQualifiedName, value.ToString());
        }

        object ObjFromString(string value)
        {
            if (value.IndexOf(':') < 0) return null;

            string typeName = value.Substring(0, value.IndexOf(':'));
            string data = value.Substring(value.IndexOf(':') + 1);

            Type type = Type.GetType(typeName);
            if (type == null) return null;

            ConstructorInfo constructor = type.GetConstructor(new[] { typeof(string) });
            if (constructor == null) return null;

            object instance = constructor.Invoke(new object[] { data });
            return instance;
        }
    }
}
